/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package phd.dupenois.changeidentifiers.utils;

import java.util.ArrayList;
import java.util.Iterator;
import phd.dupenois.changeidentifiers.dotpatterns.Dot;
import phd.dupenois.changeidentifiers.dotpatterns.DotPattern;
import phd.dupenois.changeidentifiers.dotpatterns.DotPatternNotFinalised;
import uk.co.dupenois.geometry.Coordinate;
import uk.co.dupenois.geometry.CoordinateList;
import uk.co.dupenois.geometry.Line;

/**
 *
 * @author mpd209
 */
public class PropertiesTool {

    public static BoundingBox getBoundingBox(DotPattern pattern) throws DotPatternNotFinalised{
        BoundingBox box;
        if(pattern.getPropertiesHolder().has("bounding_box")){
            box = pattern.getPropertiesHolder().get("bounding_box", BoundingBox.class);
        }else{
            Coordinate bottomLeft = pattern.getList().getBoxBottomLeft();
            Coordinate topLeft = pattern.getList().getBoxTopLeft();
            Coordinate topRight = pattern.getList().getBoxTopRight();
            Coordinate bottomRight = pattern.getList().getBoxBottomRight();
            box = new BoundingBox(bottomLeft, topLeft, topRight, bottomRight);
            pattern.getPropertiesHolder().set("bounding_box", box);
        }
        return box;
    }

    public static double getBoundingBoxArea(DotPattern pattern) throws DotPatternNotFinalised{
        double area;
        if(pattern.getPropertiesHolder().has("bounding_box_area")){
            area = pattern.getPropertiesHolder().get("bounding_box_area", double.class);
        }else{
            BoundingBox box = getBoundingBox(pattern);
            double height = box.getBottomLeft().getDistance(box.getTopLeft());
            double width = box.getBottomLeft().getDistance(box.getBottomRight());
            area = height*width;
            pattern.getPropertiesHolder().set("bounding_box_area", area);
        }
        return area;
    }
    public static double getBoundingBoxDiagonal(DotPattern pattern) throws DotPatternNotFinalised{
        double diagonal;
        if(pattern.getPropertiesHolder().has("bounding_box_diagonal")){
            diagonal = pattern.getPropertiesHolder().get("bounding_box_diagonal", double.class);
        }else{
            BoundingBox box = getBoundingBox(pattern);
            diagonal = box.getBottomLeft().getDistance(box.getTopRight());
            pattern.getPropertiesHolder().set("bounding_box_area", diagonal);
        }
        return diagonal;
    }
    public static Coordinate getCentroid(DotPattern pattern){
        Coordinate centroid;
        if(pattern.getPropertiesHolder().has("centroid")){
            centroid = pattern.getPropertiesHolder().get("centroid", Coordinate.class);
        }else{
            Dot[] dots = pattern.getDots();
            double x = 0;
            double y = 0;
            for(Dot dot : dots){
                x+= dot.getX();
                y+= dot.getX();
            }
            double avgX = x/dots.length;
            double avgY = y/dots.length;
            centroid = new Coordinate(avgX, avgY);
            pattern.getPropertiesHolder().set("centroid", centroid);
        }
        return centroid;
    }
    public static Line getLineOfBestFit(DotPattern pattern){
        Line line = null;
        if(pattern.getPropertiesHolder().has("line_of_best_fit")){
            line = pattern.getPropertiesHolder().get("line_of_best_fit", Line.class);
        }else{
            try {
                line = pattern.getList().getLineOfBestFit();
                pattern.getPropertiesHolder().set("line_of_best_fit", line);
            } catch (DotPatternNotFinalised ex) {
                //Shouldn't happen
            }
        }
        return line;
    }

    public static Line getLineBetweenFurthestCharactersticDots(DotPattern pattern){
        Line line = null;
        if(pattern.getPropertiesHolder().has("line_between_characteristic")){
            line = pattern.getPropertiesHolder().get("line_between_characteristic", Line.class);
        }else{
            try {
                CoordinateList list = pattern.getList();
                ArrayList<Coordinate> coords = new ArrayList<Coordinate>();
                coords.add(list.getFirstFromX()); //xMin
                coords.add(list.getLastFromX());  //xMax
                coords.add(list.getFirstFromY()); //yMin
                coords.add(list.getLastFromX());  //yMax
                Iterator<Coordinate> iter = coords.iterator();
                Iterator<Coordinate> iter2;
                Coordinate coord;
                Coordinate compare;
                Coordinate start = null;
                Coordinate end = null;
                double maxDistance = 0;
                double distance = 0;
                while(iter.hasNext()){
                    coord = iter.next();
                    iter2 = coords.iterator();
                    while(iter2.hasNext()){
                        compare = iter2.next();
                        if(!compare.equals(coord)){
                            distance = compare.getDistanceSquared(coord);
                            if(distance>maxDistance){
                                maxDistance = distance;
                                start = coord;
                                end = compare;
                            }
                        }
                    }
                    iter.remove();
                }
                if(start==null||end==null){
                    line = new Line(new Coordinate(0,0), new Coordinate(10, 0), true);
                }else{
                    line = new Line(start, end, true);
                }
                pattern.getPropertiesHolder().set("line_between_characteristic", line);
            } catch (DotPatternNotFinalised ex) {
                //Shouldn't happen
            }
        }
        return line;
    }
//    This is wrong!
//    Quickest way seems to be convex hull
//    public static double getDiameter(DotPattern pattern) throws DotPatternNotFinalised{
//        //Diameter is the greatest distance between any two points, these points
//        //have to be extremal points so only need to check those 4
//        double diameter;
//        if(pattern.getPropertiesHolder().has("diameter")){
//            diameter = pattern.getPropertiesHolder().get("diameter", double.class);
//        }else{
//    //        Coordinate[] coordinates = new Coordinate[4];
//    //        coordinates[0] = pattern.getList().getFirstFromX();
//    //        coordinates[1] = pattern.getList().getLastFromX();
//    //        coordinates[2] = pattern.getList().getFirstFromY();
//    //        coordinates[3] = pattern.getList().getLastFromY();
//            ArrayList<Coordinate> coordinateList = new ArrayList<Coordinate>();
//            coordinateList.add(pattern.getList().getFirstFromX());
//            coordinateList.add(pattern.getList().getLastFromX());
//            coordinateList.add(pattern.getList().getFirstFromY());
//            coordinateList.add(pattern.getList().getLastFromY());
//
//            Iterator<Coordinate> iter = coordinateList.iterator();
//            Iterator<Coordinate> iter2;
//            Coordinate c1;
//            Coordinate c2;
//            diameter = 0;
//            double distance = 0;
//            while(iter.hasNext()){
//                c1 = iter.next();
//                iter2 = coordinateList.iterator();
//                while(iter2.hasNext()){
//                    c2 = iter2.next();
//                    distance = c1.getDistance(c2);
//                    if(distance>diameter) diameter = distance;
//                }
//                iter.remove();
//            }
//            pattern.getPropertiesHolder().set("diameter", diameter);
//        }
//        return diameter;
//    }

    public static double getDensityByVariance(DotPattern pattern) throws DotPatternNotFinalised{
        double density;
        if(pattern.getPropertiesHolder().has("density_by_variance")){
            density = pattern.getPropertiesHolder().get("density_by_variance", double.class);
        }else{
            double cardinality = pattern.size();
            double variance = PropertiesTool.getVarianceFromCentroid(pattern);
            density = cardinality/variance;
            pattern.getPropertiesHolder().set("density_by_variance", density);
        }
        return density;
    }

    public static double getDensity(DotPattern pattern) throws DotPatternNotFinalised{
        double density;
        if(pattern.getPropertiesHolder().has("density")){
            density = pattern.getPropertiesHolder().get("density", double.class);
        }else{
            double boundingBoxArea = PropertiesTool.getBoundingBoxArea(pattern);
            density = pattern.size()/boundingBoxArea;
            pattern.getPropertiesHolder().set("density", density);
        }
        return density;
    }

    /**
     *
     * @param pattern
     * @return
     */
    public static double getVarianceFromCentroid(DotPattern pattern){
        double variance;
        if(pattern.getPropertiesHolder().has("variance_from_centroid")){
            variance = pattern.getPropertiesHolder().get("variance_from_centroid", double.class);
        }else{
            Coordinate centroid = PropertiesTool.getCentroid(pattern);
            Dot[] dots = pattern.getDots();
            double x = 0;
            double y = 0;
            variance = 0;
            for(Dot dot : dots){
                variance += Math.pow((dot.getDistance(centroid)), 2);
            }
            variance = variance/dots.length;
            pattern.getPropertiesHolder().set("variance_from_centroid", variance);
        }
        return variance;
    }
    /**
     * 
     * @param pattern
     * @return
     */
    public static double getStandardDeviationFromCentroid(DotPattern pattern){
        double standardDeviation;
        if(pattern.getPropertiesHolder().has("standard_deviation_from_centroid")){
            standardDeviation = pattern.getPropertiesHolder().get("standard_deviation_from_centroid", double.class);
        }else{
            Coordinate centroid = PropertiesTool.getCentroid(pattern);
            Dot[] dots = pattern.getDots();
            double x = 0;
            double y = 0;
            standardDeviation = 0;
            for(Dot dot : dots){
                standardDeviation += Math.pow((dot.getDistance(centroid)), 2);
            }
            standardDeviation = Math.sqrt(standardDeviation/dots.length);
            pattern.getPropertiesHolder().set("standard_deviation_from_centroid", standardDeviation);
        }
        return standardDeviation;
    }

}
